// 定义类型：事件名称类型：字符串、数字、symbol
export type EventNameType = string | number | symbol
// 定义类型：事件回调
export type EventCallback = (...args: Array<any>) => any
// 监听停止
export type ListenStopHandle = () => void

export default class Emitter {
  // 事件缓存
  // 一个事件名对应多个回调
  private eventMap: Map<EventNameType, Array<EventCallback>>
  
  constructor () {
    this.eventMap = new Map()
  }

  /**
   * 
   * @param name 事件名称
   * @param callback 回调
   */
  public on = (name: EventNameType, callback: EventCallback): ListenStopHandle => {
    // 如果第一次监听该事件
    if (!this.eventMap.has(name)) {
      this.eventMap.set(name, [])
    }

    const callbackList = this.eventMap.get(name)!
    // 缓存回调
    callbackList.push(callback)

    // 返回：移除该监听
    // let result = $on('xxx',function);
    // result() 移除该监听
    return () => this.off(name, callback)
  }

  /**
   * 执行逻辑
   * <p>
   * 1、监听事件
   * 2、当触发事件时，先移除监听
   * 3、执行回调
   * </p>
   * @param name 
   * @param callback 
   */
  public once = (name: EventNameType, callback: EventCallback): ListenStopHandle => {
    // 回调包装器
    const listener = (...args: Array<any>) => {
      // 在执行回调之前，移除该事件的监听
      // 会根据listener移除回调列表中的一个
      this.off(name, listener)
      callback(...args)
    }
    // listener在这里是回调，
    // 在listener方法里：执行用户传递的回调之前，移除了监听，所以once只执行一次
    this.on(name, listener)

    // 同上
    return () => this.off(name, listener)
  }

  // 
  public emit =  (name: EventNameType, ...args: Array<any>): void => {
    // 获取到回调表，执行回调
    const callbackList = this.eventMap.get(name)
    if (Array.isArray(callbackList)) {
      callbackList.forEach(cb => {
        typeof cb === 'function' && cb(...args)
      })
    }
  }

  // 
  public off = (name?: EventNameType, callback? : EventCallback): void => {
    if (!name) {
      // 移除所有，如: $off()、$off(false)、$off(null)、$off(0)
      this.eventMap.clear()
    } else if (typeof callback === 'function') {
      // 移除监听 by callback
      if (this.eventMap.has(name)) {
        const eventList = this.eventMap.get(name)!.filter(v => v !== callback)
        this.eventMap.set(name, eventList)
      }
    } else {
      // 移除监听 by name
      this.eventMap.delete(name)
    }
  }
}
